/* @flow */

import config from '../config'
import Watcher from '../observer/watcher'
import { mark, measure } from '../util/perf'
import { createEmptyVNode } from '../vdom/vnode'
import { observerState } from '../observer/index'
import { updateComponentListeners } from './events'
import { resolveSlots } from './render-helpers/resolve-slots'

import {
  warn,
  noop,
  remove,
  handleError,
  emptyObject,
  validateProp
} from '../util/index'

//@todo 该变量的作用是什么？是如何使用的？
export let activeInstance: any = null
export let isUpdatingChildComponent: boolean = false

export function initLifecycle (vm: Component) {
  const options = vm.$options

  // locate first non-abstract parent
  //在选项中指定当前实例的父实例
  //此处代码用于定位第一个不是抽象的父实例
  //比如keep-alive和transition组件的abstract属性就为true，
  //这些组件自身不会渲染为一个dom元素，也不会出现在父组件链中，也不会存放任何子组件
  let parent = options.parent
  //如果父组件存在，并且当前组件不是抽象的
  if (parent && !options.abstract) {
    //找到当前组件的第一个非抽象的父组件的Vue实例
    while (parent.$options.abstract && parent.$parent) {
      parent = parent.$parent
    }
    parent.$children.push(vm)
  }

  vm.$parent = parent
  vm.$root = parent ? parent.$root : vm

  vm.$children = []
  vm.$refs = {}

  //渲染与重渲染的watcher
  vm._watcher = null
  ///与keep-alive组件相关的属性，会在keep-alive组件的非直接子组件上设置，
  //当这些子孙组件变为未激活状态时会被设置为true
  vm._inactive = null
  //与keep-alive组件相关的属性，会在keep-alive组件的直接子组件上设置，
  //当该子组件变为未激活状态时会被设置为true
  vm._directInactive = false
  //标记是否挂载成功
  vm._isMounted = false
  vm._isDestroyed = false
  vm._isBeingDestroyed = false
}

export function lifecycleMixin (Vue: Class<Component>) {
  //对于hydrating参数，初次渲染时是mountComponent方法的参数的值，之后的重渲染都是false，
  //详情可查看mountComponent方法中的描述。
  Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
    const vm: Component = this
    //只有挂载成功之后的更新才会调用相应的beforeUpdate生命周期函数，
    //也就是重渲染时才会调用beforeUpdate生命周期函数
    //beforeUpdate钩子函数是在创建vnode对象之后运行的
    if (vm._isMounted) {
      callHook(vm, 'beforeUpdate')
    }
    const prevEl = vm.$el
    const prevVnode = vm._vnode
    const prevActiveInstance = activeInstance
    activeInstance = vm
    //保存最新的虚拟dom对象
    vm._vnode = vnode
    // Vue.prototype.__patch__ is injected in entry points
    // based on the rendering backend used.
    if (!prevVnode) {
      // initial render
      // 初次渲染时会运行的语句块
      vm.$el = vm.__patch__(
        vm.$el, vnode, hydrating, false /* removeOnly */,
        //_parentElm属性和_refElm属性是在core/instance/create-component.js-->createComponentInstanceForVnode方法中设置的，
        //在创建子组件时注入。用于保持子组件元素与父组件元素的子父级关系。
        vm.$options._parentElm,
        vm.$options._refElm
      )
      // no need for the ref nodes after initial patch
      // this prevents keeping a detached DOM tree in memory (#5851)
      vm.$options._parentElm = vm.$options._refElm = null
    } else {
      // updates
      // 重渲染时会运行的语句块
      vm.$el = vm.__patch__(prevVnode, vnode)
    }
    activeInstance = prevActiveInstance
    // update __vue__ reference
    if (prevEl) {
      prevEl.__vue__ = null
    }
    if (vm.$el) {
      vm.$el.__vue__ = vm
    }
    // if parent is an HOC, update its $el as well
    // 也就是说在父组件中，当前子组件在父组件中是根元素，
    // 示例：
    // 子组件为child，父组件为parent，父组件的template为'<child>...</child>'
    if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
      vm.$parent.$el = vm.$el
    }
    // updated hook is called by the scheduler to ensure that children are
    // updated in a parent's updated hook.
  }

  Vue.prototype.$forceUpdate = function () {
    const vm: Component = this
    if (vm._watcher) {
      vm._watcher.update()
    }
  }

  //销毁vue实例，对当前vue实例做一些清理的工作，主要做的工作有：
  //1）从父组件的children数组中移除当前组件
  //2）销毁当前vue实例上的watcher
  //3）移除当前实例上的所有事件监听
  //可见，并没有销毁vue实例关联的$el元素，所以需要手工从dom树中移除
  Vue.prototype.$destroy = function () {
    const vm: Component = this
    if (vm._isBeingDestroyed) {
      return
    }
    callHook(vm, 'beforeDestroy')
    vm._isBeingDestroyed = true
    // remove self from parent
    const parent = vm.$parent
    //从父组件中删除当前组件实例
    if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
      remove(parent.$children, vm)
    }
    // teardown watchers
    //销毁渲染与重渲染的watcher
    if (vm._watcher) {
      vm._watcher.teardown()
    }
    let i = vm._watchers.length
    while (i--) {
      vm._watchers[i].teardown()
    }
    // remove reference from data ob
    // frozen object may not have observer.
    if (vm._data.__ob__) {
      vm._data.__ob__.vmCount--
    }
    // call the last hook...
    vm._isDestroyed = true
    // invoke destroy hooks on current rendered tree
    vm.__patch__(vm._vnode, null)
    // fire destroyed hook
    callHook(vm, 'destroyed')
    // turn off all instance listeners.
    vm.$off()
    // remove __vue__ reference
    if (vm.$el) {
      vm.$el.__vue__ = null
    }
  }
}

//该方法用于真正的挂载组件
export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el
  if (!vm.$options.render) {
    vm.$options.render = createEmptyVNode
    if (process.env.NODE_ENV !== 'production') {
      /* istanbul ignore if */
      if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
        vm.$options.el || el) {
        warn(
          'You are using the runtime-only build of Vue where the template ' +
          'compiler is not available. Either pre-compile the templates into ' +
          'render functions, or use the compiler-included build.',
          vm
        )
      } else {
        warn(
          'Failed to mount component: template or render function not defined.',
          vm
        )
      }
    }
  }
  //beforeMount是在创建当前虚拟dom对象之前调用的
  callHook(vm, 'beforeMount')

  let updateComponent
  /* istanbul ignore if */
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    updateComponent = () => {
      const name = vm._name
      const id = vm._uid
      const startTag = `vue-perf-start:${id}`
      const endTag = `vue-perf-end:${id}`

      mark(startTag)
      const vnode = vm._render()
      mark(endTag)
      measure(`${name} render`, startTag, endTag)

      mark(startTag)
      vm._update(vnode, hydrating)
      mark(endTag)
      measure(`${name} patch`, startTag, endTag)
    }
  } else {
    updateComponent = () => {
      vm._update(vm._render(), hydrating)
    }
  }

  //updateComponent就相当于computed中的属性的函数，而在updateComponent方法中，会运行_render()函数，
  //该函数会获得最新的虚拟dom对象，并调用相应的get方法，这样就会添加监听，所以当属性改变时，就实现了重渲染。详情可见：
  //http://hcysun.me/2017/03/03/Vue%E6%BA%90%E7%A0%81%E5%AD%A6%E4%B9%A0/
  vm._watcher = new Watcher(vm, updateComponent, noop)
  //只有初次渲染时，hydrating使用mountComponent方法传入的原始值，
  //之后的重新渲染，hydrating都为false
  hydrating = false

  // manually mounted instance, call mounted on self
  // mounted is called for render-created child components in its inserted hook
  // 如上以及initRender方法中所示，对于通过new Vue()的方式创建的vue实例，其$vnode属性无值，
  // 但是对于子组件实例来说，其$vnode属性是有值的，所以mounted钩子函数不会在此处调用，而是在
  // create-component.js --> insert hook中调用的mounted钩子函数。
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}

//该方法在子组件patch时被调用，用于在patch过程中更新Vue实例的相关信息。也就是存在oldVnode和newVnode，
//newVnode需要替换oldVnode，就需要替换oldVnode对应的Vue实例对oldVnode的引用。以及更新Vue中的某些属性，
//比如props对应的属性、$listener等
//总之，主要是根据新的虚拟dom对象更新Vue实例。
export function updateChildComponent (
  vm: Component,
  propsData: ?Object, //从父组件的模板中通过props传递到子组件的属性
  listeners: ?Object, //从父组件的模板添加的事件监听
  parentVnode: VNode,
  renderChildren: ?Array<VNode>
) {
  if (process.env.NODE_ENV !== 'production') {
    //设置为ture，保证在修改props，$attrs，$listeners时不会报错
    isUpdatingChildComponent = true
  }

  // determine whether component has slot children
  // we need to do this before overwriting $options._renderChildren
  const hasChildren = !!(
    renderChildren ||               // has new static slots
    vm.$options._renderChildren ||  // has old static slots
    parentVnode.data.scopedSlots || // has new scoped slots
    vm.$scopedSlots !== emptyObject // has old scoped slots
  )

  //更新Vue实例，将oldVnode更新为newVnode。
  vm.$options._parentVnode = parentVnode
  vm.$vnode = parentVnode // update vm's placeholder node without re-render

  if (vm._vnode) { // update child tree's parent
    vm._vnode.parent = parentVnode
  }
  vm.$options._renderChildren = renderChildren

  // update $attrs and $listensers hash
  // these are also reactive so they may trigger child update if the child
  // used them during render
  vm.$attrs = parentVnode.data && parentVnode.data.attrs
  vm.$listeners = listeners

  // update props
  if (propsData && vm.$options.props) {
    observerState.shouldConvert = false
    const props = vm._props
    const propKeys = vm.$options._propKeys || []
    for (let i = 0; i < propKeys.length; i++) {
      const key = propKeys[i]
      props[key] = validateProp(key, vm.$options.props, propsData, vm)
    }
    observerState.shouldConvert = true
    // keep a copy of raw propsData
    vm.$options.propsData = propsData
  }

  // update listeners
  if (listeners) {
    const oldListeners = vm.$options._parentListeners
    vm.$options._parentListeners = listeners
    updateComponentListeners(vm, listeners, oldListeners)
  }
  // resolve slots + force update if has children
  if (hasChildren) {
    vm.$slots = resolveSlots(renderChildren, parentVnode.context)
    vm.$forceUpdate()
  }

  if (process.env.NODE_ENV !== 'production') {
    isUpdatingChildComponent = false
  }
}

//查看vm实例所在上层组件树是否是未激活状态
function isInInactiveTree (vm) {
  while (vm && (vm = vm.$parent)) {
    if (vm._inactive) return true
  }
  return false
}

//与keep-alive组件相关的处理，用于激活keep-alive下的直接子组件及其子孙组件
export function activateChildComponent (vm: Component, direct?: boolean) {
  //猜测：direct说明vm实例对应的组件是keep-alive组件的直接子组件
  if (direct) {
    //@todo 猜测：keep-alive组件的直接子组件才会设置该属性，是一种性能优化，如下所述。
    vm._directInactive = false
    if (isInInactiveTree(vm)) {
      return
    }        //vm._directInactive猜测：是一个性能优化，如果keep-alive的直接子组件A是未激活状态，则也不需要改变组件A的
             //子组件的状态，另外，direct为false，并且vm._directInactive为true的可能情况是keep-alive组件内嵌套keep-alive组件。
  } else if (vm._directInactive) {
    return
  }
  if (vm._inactive || vm._inactive === null) {
    vm._inactive = false
    for (let i = 0; i < vm.$children.length; i++) {
      activateChildComponent(vm.$children[i])
    }
    callHook(vm, 'activated')
  }
}

//与keep-alive组件相关的处理，使keep-alive下的直接子组件及其子孙组件变为未激活状态
export function deactivateChildComponent (vm: Component, direct?: boolean) {
  if (direct) {
    vm._directInactive = true
    //isInInactiveTree方法会查看vm实例的祖先组件中是否存在未激活状态的组件，
    //如果存在，则说明当前组件及其子组件原本就处在未激活的树中，所以其子组件也无需设置为未激活状态
    if (isInInactiveTree(vm)) {
      return
    }
  }
  if (!vm._inactive) {
    vm._inactive = true
    for (let i = 0; i < vm.$children.length; i++) {
      deactivateChildComponent(vm.$children[i])
    }
    //每一个keep-alive组件中的子孙元素在inactive时都会调用deactivated钩子
    callHook(vm, 'deactivated')
  }
}

export function callHook (vm: Component, hook: string) {
  const handlers = vm.$options[hook]
  if (handlers) {
    for (let i = 0, j = handlers.length; i < j; i++) {
      try {
        handlers[i].call(vm)
      } catch (e) {
        handleError(e, vm, `${hook} hook`)
      }
    }
  }

  //_hasHookEvent是在initEvent中设置的
  if (vm._hasHookEvent) {
    vm.$emit('hook:' + hook)
  }
}
